/* spearl/spearl.c 
 * 
 * This file is part of spearl. 
 * 
 * Spearl is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version. 
 * 
 * Spearl is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with spearl. If not, see <https://www.gnu.org/licenses/>
 */ 




#include <spearl/spearl.h>
#include <spearl/error.h>
#include <spearl/sys.h>

int __locally spl_input_size;
char __locally *spl_input;

char __visible lbracket = '(', rbracket = ')';

struct spl_klist_node __visible wordlist = SPL_KLIST_NODE_INIT(&wordlist);
struct spl_klist_node __visible spl_macros = SPL_KLIST_NODE_INIT(&spl_macros);
struct spearl_object __visible root_object;

#define callfn(fn) if (fn == -1) return -1

int spearl_init(void) {
	root_object.type = LIST;
	root_object.list = spl_list_alloc();

	spl_klist_init(&root_object.node);
	spl_klist_init(&root_object.list->objs);
	spl_klist_init(&root_object.list->node);
	
	root_object.list->nobjs = 0;

	spl_builtin_init();

	return 0;
}

static char *join_to_spl_input(char *s, int len) {
	return strcat(check_alloc((spl_input = realloc(spl_input, (spl_input_size += (len = strlen(s)))))), s);
}

static struct spearl_word *new_word(char *p, char *f) {
	struct spearl_word *word;

	word = spl_word_alloc();

	spl_klist_init(&word->node);
	spl_klist_add(&wordlist, &word->node);

	word->p = p;
	word->len = f - p;

	return word;
}

static inline char *skip_spaces(char *p) {
	while (*p && isspace(*p)) 
		p++;

	if (!*p) 
		return NULL;

	return p;
}

#define notfirstchar() (save < p)

static int read_words(char *s) {
	char *p, *save;
	spl_make_state();

	save = p = s;
	while (*p) {
		if (*p == lbracket) {
			if (notfirstchar()) {
				new_word(save, p);
				save = p;
			}

			p++;

			new_word(save, p);
			save = p;
			spl_state_left();

			continue;
		} 
		
		else if (*p == rbracket) {
			if (!spl_current_state_level()) 
				return spearl_error(SPL_BAD_BRACKETS);

			new_word(save, p);

			p++;
			save = p;
			spl_state_right();

			continue;
		}

		else if (isspace(*p)) {
			new_word(save, p);
			p = save = skip_spaces(p);
		}

		else 
			++p;
	}

	if (spl_current_state_level()) 
		return spearl_error(SPL_BAD_BRACKETS);

	if (notfirstchar()) 
		new_word(save, p);

	return 0;
}

static int join_words(char *s) {
	join_to_spl_input(s, strlen(s));
	callfn(read_words(s));

	return 0;
}

static struct spearl_object *read_word_object(struct spearl_word *word, 
		struct spl_klist_node **nout) {
	struct spearl_object *obj;

	obj = spl_object_alloc();

	spl_klist_init(&obj->node);
	obj->type = WORD;
	obj->word = word;

	*nout = word->node.next;
	spl_klist_del(&wordlist, &word->node);

	return obj;
}

static struct spearl_object *read_list_object(struct spearl_word *word, 
		struct spl_klist_node **nout) {
	struct spearl_list 	*list;
	struct spearl_object 	*object, *obj;
	struct spl_klist_node 	*n, *next;

	object = spl_object_alloc();
	list = spl_list_alloc();

	spl_klist_init(&object->node);
	spl_klist_init(&list->objs);
	spl_klist_init(&list->node);

	object->type = LIST;
	object->list = list;
	list->nobjs = 0;

	spl_klist_del(&wordlist, &word->node);

	for (n = word->node.next; n != &wordlist; n = next) {
		next = n->next;
		word = word_of_node(n);

		if (*word->p == rbracket) {
			*nout = next;
			spl_klist_del(&wordlist, n);

			return object;
		}

		else if (*word->p == lbracket) 
			obj = read_list_object(word, &next);
		else 
			obj = read_word_object(word, &next);

		spl_klist_add(&list->objs, &obj->node);
	}

	return object;
}

static int build_list(void) {
	struct spl_klist_node 	*n;
	struct spearl_word 	*word;
	struct spearl_object 	*obj;

	n = wordlist.next;

	while (!spl_klist_empty(&wordlist)) {
		word = word_of_node(n);

		if (*word->p != lbracket) 
			return spearl_error(SPL_INVALID_LIST);

		if (!(obj = read_list_object(word, &n))) 
			return -1;
		
		spl_klist_add(&root_object.list->objs, &obj->node);
		root_object.list->nobjs++;
	}

	return 0;
}

struct spearl_mcall *collect_args(struct spearl_list *list) {
	struct spearl_mcall 		*mcall;
	struct spearl_object 		*obj;
	struct spearl_word 		*word;
	struct spearl_mcall_arg 	*arg;
	struct spl_klist_node 		*n;

	spl_make_state();

	mcall = spl_mcall_alloc();
	mcall->list = list;
	mcall->dest = NULL;
	mcall->mname = NULL;

	spl_klist_init(&mcall->args);

	for (n = list->objs.next; n != &list->objs; n = n->next) {
		obj = object_of_node(n);

		if (obj->type == WORD) {
			word = obj->word;

			if (spl_state_in()) {
				(void) spearl_error(SPL_LIST_NFOUND);
				return NULL;
			}

			/* we won't do any check, to make sure we didn't set it. */
			switch (*word->p) {
			case '#':
				mcall->mname = obj;
				break;

			case '~':
				arg = spl_arg_alloc();
				spl_klist_init(&arg->node);
				spl_klist_add(&mcall->args, &arg->node);

				arg->object = obj;

				if (word->len == 1) {
					arg->type = LIST;
					spl_set_state(IN);
				}

				else {
					arg->type = WORD;
					arg->p = strndup(word->p + 1, word->len - 1);
				}

				break;

			case '@':
				mcall->dest = obj;
				break;

			default:
				(void) spearl_error(SPL_UNEXPECTED_WORD);
				return NULL;
			}
		}

		else {
			if (spl_state_in()) {
				arg->list = obj->list;
				spl_set_state(OUT);
			}
			
			else {
				(void) spearl_error(SPL_UNEXPECTED_LIST);
				return NULL;
			}
		}
	}

	return mcall;
}


static inline int mcall_valid(struct spearl_mcall *mcall) {
	if (mcall->dest && mcall->mname) 
		return 1;

	return 0;
}


static struct spearl_macro *find_macro(struct spearl_mcall *mcall) {
	struct spl_klist_node *n;
	struct spearl_macro *macro;

	for (n = spl_macros.next; n != &spl_macros; n = n->next) {
		macro = macro_of_node(n);

		if (!strncmp(macro->name, mcall->mname->word->p + 1, mcall->mname->word->len - 1)) 
			return macro;
	}
	
	return NULL;
}


static int do_expand_macro(struct spearl_list *list, struct spl_klist_node *node) {
	struct spearl_mcall *mcall;
	struct spearl_macro *macro;

	if (!(mcall = collect_args(list))) 
		return -1;

	if (!mcall_valid(mcall)) 
		return spearl_error(SPL_INCOMPLETE_CALL);

	if (!(macro = find_macro(mcall))) 
		return spearl_error(SPL_MACRO_NEXISTS);

	if (macro->type == BUILTIN) 
		callfn(expand_builtin_macro(macro, mcall, node));

	return 0;
}


static int expand_calls(void) {
	struct spl_klist_node 	*n;
	struct spearl_object 	*obj;

	for (n = root_object.list->objs.next; n != &root_object.list->objs; n = n->next) {
		obj = object_of_node(n);

		if (obj->type != LIST) 
			return spearl_error(SPL_INVALID_LIST);

		callfn(do_expand_macro(obj->list, n));
	}

	return 0;
}

int spearl_join(char *s) {
	callfn(join_words(s));
	callfn(build_list());
	callfn(expand_calls());

	return 0;
}

